﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace UI.NetworkDiagram
{
    public class ForceDirected : IGraphRenderer
    {
        private int maxIteration = 5000;
        private bool isStopped=false;
        private double minVerticeWidth = 20;
        private double defaultEdgeLength = 10;

        #region Implementation of IGraphRenderer

        public event OnFinishedGraphRendering GraphRenderFinished;
        public bool IsStopped
        {
            get
            {
                return isStopped;
            }
            set
            {
                if(this.isStopped !=value)
                {
                    this.isStopped = value;
                    if(this.isStopped)
                    {
                        if (this.GraphRenderFinished != null)
                            this.GraphRenderFinished();
                    }
                }
            }
        }

        public void UpdatePositions(
            List<Vertice> vertices, List<Edge> edges, 
            Rect constraintRect, int iteration, 
            double attractForceConst, 
            double repellingForceConst)
        {
            if(iteration>this.maxIteration)
            {
                this.IsStopped = true;
                return;
            }

            double repulsionRange = Math.Min(constraintRect.Width, constraintRect.Height)/4;
            double maxDistance = 0;
            double minDistance = double.PositiveInfinity;
            foreach(Edge e in edges)
            {
                if(e.Length>maxDistance)
                    maxDistance = e.Length;
                if(e.Length<minDistance)
                    minDistance = e.Length;
            }
            double scaleFactor = maxDistance*2/Math.Max(constraintRect.Width, constraintRect.Height);
            int numOfMoves = 0;
            foreach(Vertice v in vertices)
            {
                Vector newForce=new Vector();
                if(v.CanMove)
                {
                    // attraction 
                    foreach(Edge e in edges)
                    {
                        double distance = e.Length > 0 ? e.Length/scaleFactor : defaultEdgeLength;
                        if(e.FromVertice==v)
                        {
                            double d = (e.FromVertice.Position - e.ToVertice.Position).Length;
                            d = d - (e.FromVertice.Ball.ActualWidth + e.ToVertice.Ball.ActualWidth) / 2;
                            if(d<=0)
                                continue;

                            double springForce = attractForceConst*Math.Log(d/distance);
                            double fx = springForce*(e.ToVertice.Position.X - e.FromVertice.Position.X)/d;
                            double fy = springForce*(e.ToVertice.Position.Y - e.FromVertice.Position.Y)/d;
                            if (fx < -repulsionRange)
                                fx = -repulsionRange;
                            else if (fx > repulsionRange)
                                fx = repulsionRange;
                            if (fy < -repulsionRange)
                                fy = -repulsionRange;
                            else if (fy > repulsionRange)
                                fy = repulsionRange;
                            newForce +=new Vector(fx,fy);
                        }
                    }

                    // repelling
                    foreach(Vertice v2 in vertices)
                    {
                        if(v!=v2)
                        {
                            double d = (v.Position - v2.Position).Length;
                            if (d == 0)
                                d = 10;
                            if(d>0 && d<repulsionRange+v.Ball.ActualWidth/2+v2.Ball.ActualWidth/2)
                            {
                                double fx = Math.Pow(repulsionRange/2, 2)*((v.Position.X - v2.Position.X)/d);
                                double fy = Math.Pow(repellingForceConst/d, 2)*((v.Position.Y - v2.Position.Y)/d);
                                if (fx < -repulsionRange)
                                    fx = -repulsionRange;
                                else if (fx > repulsionRange)
                                    fx = repulsionRange;
                                if (fy < -repulsionRange)
                                    fy = -repulsionRange;
                                else if (fy > repulsionRange)
                                    fy = repulsionRange;
                                newForce +=new Vector(fx,fy);
                            }
                        }
                    }

                    if (newForce.X > 3 || newForce.Y > 3)
                        numOfMoves++;

                    double dampingFactor = ((double) this.maxIteration - iteration)/this.maxIteration;
                    Point forceDrag = newForce.ToPoint();
                    if (forceDrag.X > repulsionRange / 2)
                        forceDrag.X = repulsionRange / 2;
                    if (forceDrag.Y > repulsionRange / 2)
                        forceDrag.Y = repulsionRange / 2;
                    double rx = 10;
                    double ry = 10;
                    rx = v.Ball.ActualWidth / 2;
                    ry = v.Ball.ActualHeight / 2;
                    rx *= dampingFactor;
                    ry *= dampingFactor;
                    double x = v.Position.X + forceDrag.X;
                    double y = v.Position.Y + forceDrag.Y;
                    if (x < constraintRect.Left + rx)
                        x = constraintRect.Left + rx;
                    if (x > constraintRect.Right - rx)
                        x = constraintRect.Right - rx;
                    if (y < constraintRect.Top + ry)
                        y = constraintRect.Top + ry;
                    if (y > constraintRect.Bottom - ry)
                        y = constraintRect.Bottom - ry;
                    v.Position = new Vector(x, y);
                }
            }

            if (numOfMoves == 0)
            {
                this.IsStopped = true;
            }
        }

        public void RenderEdges(List<Vertice> vertices, List<Edge> edges)
        {
            foreach(Edge e in edges)
            {
                e.SnapEdge();
            }
        }

        #endregion
    }
}
